/**
 * 参考自 https://github.com/blowsie/Pure-JavaScript-HTML5-Parser/blob/master/htmlparser.js
 */
import { Stack } from '@kit.ArkTS'
import { HtmlNode, Options, Token, Tokens } from './common'

const startTag =
  /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/
const attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;


// 空元素 - HTML 5
const empty =
  makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");

// 块元素 - HTML 5
const block =
  makeMap("address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");

// 内联元素 - HTML 5
const inline =
  makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");

// 您可以有意保持开放状态的元素
// （并自行关闭）
const closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

// 填充了其值的属性 disabled=“disabled”
const fillAttrs =
  makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");

// 特殊元素（可以包含任何东西）
const special = makeMap("script,style");

// 先将html解析成无序结构token
export function parseToToken(html: string): Token[] {

  // 清理 html
  html = html
    // .replace(/\s+/g, ' ')// 将多个空白字符替换为一个空格
    // .replace(/>\s+</g, '><')// 去除标签之间的空白
    .replace(/(?!\S)(\s+<)/g, '<')
    .replace(/(>\s+)(?!\S)/g, '>')
    .trim(); // 去除开头和结尾的空白

  // 当前匹配位置
  let index: number = 0
  // 解析出来的token数据
  let tokens: Token[] = []
  // 未闭合的标签
  let stack: Stack<string> = new Stack()
  // 正则匹配到的结果
  let match: RegExpMatchArray | null = null
  let last: string = html

  // 处理开始标签
  function parseStartTag(_match: string, tagName: string, rest: string, unary: string | boolean): string {
    tagName = tagName.toLowerCase();
    // 标签是块元素
    // 判断上一个标签是否是内联标签，是内联标签则需要闭合,此场景经常出现在html书写不规范时没有正确对应的标签
    if (block[tagName]) {
      while (stack.peek() && inline[stack.peek()]) {
        parseEndTag("", stack.peek())
      }
    }

    // 检测自闭和标签
    if (closeSelf[tagName] && stack.peek() == tagName) {
      parseEndTag("", tagName);
    }

    // 检测空元素
    unary = empty[tagName] || !!unary;

    // 如果不是空元素，则入栈等待闭合
    if (!unary) {
      stack.push(tagName);
    }

    // 解析属性
    const attrs: object = {}
    rest.replace(attr, (_match, name: string, arg2: string, arg3: string, arg4: string) => {
      const value = arg2 ? arg2 :
        arg3 ? arg3 :
          arg4 ? arg4 :
            fillAttrs[name] ? name : true;
      attrs[name] = value
      return ""
    })

    // 如果是单闭合标签
    if (unary) {
      tokens.push({
        type: "tagStart",
        tagName,
        isClosing: true,
        attrs: attrs
      } as Tokens.TagStart)
    }
    else {
      tokens.push({ type: "tagStart", tagName, attrs: attrs } as Tokens.TagStart)
    }
    return ''
  }

  // 处理结束标签
  function parseEndTag(_match: string, tagName: string): string {
    let pos = 0;
    if (tagName !== undefined) {
      tagName = tagName.toLowerCase();
    }
    if (tagName) {
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos] == tagName) {
          break
        }
      }
    }

    if (pos >= 0) {
      // 关闭所有打开的元素，弹出栈
      for (let i = stack.length - 1; i >= pos; i--) {
        tokens.push({ type: "tagEnd", tagName: stack[i] } as Tokens.TagEnd)
        stack.pop()
      }
    }
    return ''
  }

  while (html) {
    if (!stack.peek() || !special[stack.peek()]) {
      // 匹配注释
      if (html.indexOf('<!--') === 0) {
        index = html.indexOf('-->')
        if (index > 0) {
          tokens.push({ type: "comment", value: html.substring(4, index) } as Tokens.Comment)
          html = html.substring(index + 3)
        }
      }

      // 匹配结束标签
      else if (html.indexOf('</') === 0) {
        match = html.match(endTag);
        if (match) {
          html = html.substring(match[0].length);
          match[0].replace(endTag, parseEndTag);
        }
      }

      // 匹配开始标签
      else if (html.indexOf('<') === 0) {
        match = html.match(startTag);
        if (match) {
          html = html.substring(match[0].length);
          match[0].replace(startTag, parseStartTag);
        }
      }

      else {
        index = html.indexOf("<");
        var text = index < 0 ? html : html.substring(0, index);
        html = index < 0 ? "" : html.substring(index);
        tokens.push({ type: "text", value: text } as Tokens.Text)
      }
    }
    else {
      html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.peek() + "[^>]*>"), function (_all, text) {
        text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2");
        tokens.push({ type: "text", value: text } as Tokens.Text)
        return "";
      });
      parseEndTag("", stack.peek());
    }

    if (html == last) { throw "Parse Error: " + html; }
    last = html;
  }

  // 清理剩余的标签
  parseEndTag("", "");
  return tokens
}

// 构建语法树
export function tokenTAst(parsedData: Token[]): HtmlNode {
  const root = createNode('root', 'root'); // 创建根节点
  const stack = [root]; // 栈初始化，根节点入栈
  parsedData.forEach(item => {
    const parentNode = stack[stack.length - 1]; // 获取当前栈顶的父节点

    if (item.type === 'tagStart') {
      const newNode = createNode('tag', item.tagName, item.attrs);

      // 将新节点作为当前父节点的子节点
      parentNode.children.push(newNode);

      // 如果标签不是自闭合的，且没有 isClosing 标志，则入栈
      if (!item.isClosing) {
        stack.push(newNode);
      }
    }
    else if (item.type === 'tagEnd') {
      stack.pop(); // 结束标签时出栈
    }
    else {
      // 处理文本节点、注释节点等
      const newNode = createNode(item.type, null);
      newNode.value = item.value;
      parentNode.children.push(newNode); // 直接作为当前父节点的子节点
    }
  });
  return root;
}

// 语法树转markdown
export function astToMarkdown(node: HtmlNode, options: Options = {
  indentLevel: 0,
  listIndex: 0,
  listType: '',
  parentNode: null
}): string {
  const indent = '  '.repeat(options.indentLevel);

  if (node.type === 'text') {
    return node.value.replace(/&nbsp;|&ensp;|&emsp;|&quot;|&amp;|&lt;|&gt;|&apos;|`|$/g, match => {
      switch (match) {
        case '&nbsp;':
          return ' ';
        case '&ensp;':
          return ' ';
        case '&emsp;':
          return ' ';
        case '&quot;':
          return '"';
        case '&amp;':
          return '&';
        case '&lt;':
          return '<';
        case '&gt;':
          return '>';
        case '&apos;':
          return "'";
        case '`':
          return '\\`'
        case '$':
          return '\\$'
        default:
          return match;
      }
    }) || ''
  }

  if (node.type === 'tag' || node.type === 'root') {
    const childrenMarkdown = (level: number = options.indentLevel, type: string = options.listType): string =>
    node.children.map((child, index) => {
      options.indentLevel = level
      options.listIndex = index + 1
      options.listType = type
      options.parentNode = node
      return astToMarkdown(child, options)
    }).join('')


    switch (node.name) {
      case 'h1':
        return `\n\n# ${childrenMarkdown()}\n\n`;
      case 'h2':
        return `\n\n## ${childrenMarkdown()}\n\n`;
      case 'h3':
        return `\n\n### ${childrenMarkdown()}\n\n`;
      case 'h4':
        return `\n\n#### ${childrenMarkdown()}\n\n`;
      case 'h5':
        return `\n\n##### ${childrenMarkdown()}\n\n`;
      case 'h6':
        return `\n\n###### ${childrenMarkdown()}\n\n`;
      case 'p':
        return `\n\n${childrenMarkdown()}\n\n`;
      case 'strong':
        return `**${childrenMarkdown()}**`;
      case 'em':
        return `*${childrenMarkdown()}*`;
      case 'ul':
        return `${childrenMarkdown(options.indentLevel, 'ul')}\n`;
      case 'ol':
        return `${childrenMarkdown(options.indentLevel, 'ol')}\n`;
      case 'li':
        if (options.listType === 'ol') {
          return (options.indentLevel > 0 ? '\n' : '') +
          `${options.listIndex}. ${childrenMarkdown(options.indentLevel + 1,
            'ol').trim()}`.replace(/^/gm, indent) + '\n'
        }
        else if (options.listType === 'ul') {
          return (options.indentLevel > 0 ? '\n' : '') + `- ${childrenMarkdown(options.indentLevel + 1, 'ul')
            .trim()}`.replace(/^/gm, indent) + '\n'
          // return `- ${childrenMarkdown(options.indentLevel + 1, 'ul').trim().replace(/^\s*/gm, indent)}\n`;
        }
      case 'input':
        if (node.attrs["type"] === 'checkbox') {
          return `\n- [${node.attrs["checked"] ? 'x' : ' '}] ${childrenMarkdown(options.indentLevel + 1, 'ul')
            .trim()}`.replace(/^/gm, indent) + '\n'
        }
      case 'blockquote':
        return `> ${childrenMarkdown().trim().replace(/\n/g, '\n> ')}\n\n`;
      case 'code':
        return options.parentNode.name === 'pre' ? `${childrenMarkdown()}` : `\`${childrenMarkdown()}\``;
      case 'pre':
        return `\`\`\`\n${childrenMarkdown()}\n\`\`\`\n\n`;
      case 'a':
        return `[${childrenMarkdown()}](${node.attrs['href'] || ''})`;
      case 'img':
        return `![${node.attrs['alt'] || ''}](${node.attrs['src'] || ''})`;
      case 'hr':
        return `---\n\n`;
      case 'br':
        return '\n';
      case 'table':
        // 处理整个表格
        return `\n${childrenMarkdown().trim()}\n`;
      case 'thead':
        // 处理表头
        const theadChildren = node.children[0].children;
        const colCount = theadChildren.length; // 获取列数
        const theadMarkdown = childrenMarkdown().trim();
        const separator = `|${' --- |'.repeat(colCount)}`;
        return `${theadMarkdown}\n${separator}\n`;
      case 'tbody':
        // 处理表体
        return `${childrenMarkdown()}`;
      case 'tr':
        // 处理表格行
        return `| ${childrenMarkdown().trim()} \n`;
      case 'th':
      case 'td':
        // 处理表格单元格
        return `${childrenMarkdown().trim()} | `;
      default:
        return childrenMarkdown();
    }
  }

  return '';
}


function makeMap(str: string): object {
  const obj = {}
  const items: string[] = str.split(',')
  for (let item of items) {
    obj[item] = true
  }
  return obj
}

// 创建节点
function createNode(type, tagName, attrs = {}): HtmlNode {
  return {
    type: type, // 节点类型，比如 'tagStart', 'tagEnd', 'text', 'comment'
    name: tagName, // 标签名称，比如 'div', 'p', 'button'
    attrs: attrs, // 标签属性，数组形式
    children: []     // 子节点数组
  } as HtmlNode;
}



